"""工具基类"""

from abc import ABC, abstractmethod
from typing import Dict, Any, List, Optional, Callable, get_type_hints
from pydantic import BaseModel
import inspect
import re


def tool_action(name: str = None, description: str = None):
    """装饰器：标记一个方法为可展开的工具 action

    用法:
        @tool_action("memory_add", "添加新记忆")
        def _add_memory(self, content: str, importance: float = 0.5) -> str:
            '''添加记忆

            Args:
                content: 记忆内容
                importance: 重要性分数
            '''
            ...

    Args:
        name: 工具名称（如果不提供，从方法名自动生成）
        description: 工具描述（如果不提供，从 docstring 提取）
    """
    def decorator(func: Callable):
        func._is_tool_action = True
        func._tool_name = name
        func._tool_description = description
        return func
    return decorator


class ToolParameter(BaseModel):
    """工具参数定义"""
    name: str
    type: str
    description: str
    required: bool = True
    default: Any = None


class Tool(ABC):
    """工具基类

    支持两种使用模式：
    1. 普通模式：工具作为单一实体使用
    2. 可展开模式：工具可以展开为多个独立的子工具（每个子工具对应一个功能）

    展开模式支持两种实现方式：
    - 手动定义子工具类（传统方式）
    - 使用 @tool_action 装饰器自动生成（推荐）
    """

    def __init__(self, name: str, description: str, expandable: bool = False):
        """初始化工具

        Args:
            name: 工具名称
            description: 工具描述
            expandable: 是否可展开为多个子工具
        """
        self.name = name
        self.description = description
        self.expandable = expandable

    @abstractmethod
    def run(self, parameters: Dict[str, Any]) -> str:
        """执行工具"""
        pass

    @abstractmethod
    def get_parameters(self) -> List[ToolParameter]:
        """获取工具参数定义"""
        pass

    def get_expanded_tools(self) -> Optional[List['Tool']]:
        """获取展开后的子工具列表

        默认实现：自动从标记了 @tool_action 的方法生成子工具
        子类可以重写此方法提供自定义的展开逻辑

        Returns:
            如果工具支持展开，返回子工具列表；否则返回 None
        """
        if not self.expandable:
            return None

        # 自动从装饰器标记的方法生成工具
        tools = []
        for name, method in inspect.getmembers(self, predicate=inspect.ismethod):
            if hasattr(method, '_is_tool_action'):
                tool = AutoGeneratedTool(
                    parent=self,
                    method=method,
                    name=method._tool_name,
                    description=method._tool_description
                )
                tools.append(tool)

        return tools if tools else None

    def validate_parameters(self, parameters: Dict[str, Any]) -> bool:
        """验证参数"""
        required_params = [p.name for p in self.get_parameters() if p.required]
        return all(param in parameters for param in required_params)

    def to_dict(self) -> Dict[str, Any]:
        """转换为字典格式"""
        return {
            "name": self.name,
            "description": self.description,
            "parameters": [param.dict() for param in self.get_parameters()]
        }

    def to_openai_schema(self) -> Dict[str, Any]:
        """转换为 OpenAI function calling schema 格式

        用于 FunctionCallAgent，使工具能够被 OpenAI 原生 function calling 使用

        Returns:
            符合 OpenAI function calling 标准的 schema
        """
        parameters = self.get_parameters()

        # 构建 properties
        properties = {}
        required = []

        for param in parameters:
            # 基础属性定义
            prop = {
                "type": param.type,
                "description": param.description
            }

            # 如果有默认值，添加到描述中（OpenAI schema 不支持 default 字段）
            if param.default is not None:
                prop["description"] = f"{param.description} (默认: {param.default})"

            # 如果是数组类型，添加 items 定义
            if param.type == "array":
                prop["items"] = {"type": "string"}  # 默认字符串数组

            properties[param.name] = prop

            # 收集必需参数
            if param.required:
                required.append(param.name)

        return {
            "type": "function",
            "function": {
                "name": self.name,
                "description": self.description,
                "parameters": {
                    "type": "object",
                    "properties": properties,
                    "required": required
                }
            }
        }

    def __str__(self) -> str:
        return f"Tool(name={self.name})"

    def __repr__(self) -> str:
        return self.__str__()


class AutoGeneratedTool(Tool):
    """自动生成的工具 - 从方法签名和 docstring 自动提取参数"""

    def __init__(self, parent: Tool, method: Callable, name: str = None, description: str = None):
        """初始化自动生成的工具

        Args:
            parent: 父工具实例
            method: 被装饰的方法
            name: 工具名称（如果为 None，从方法名生成）
            description: 工具描述（如果为 None，从 docstring 提取）
        """
        self.parent = parent
        self.method = method

        # 生成工具名称
        if name is None:
            # 从方法名生成：_add_memory -> parent_name_add_memory
            method_name = method.__name__.lstrip('_')
            name = f"{parent.name}_{method_name}"

        # 提取描述
        if description is None:
            description = self._extract_description_from_docstring()

        super().__init__(name=name, description=description)

        # 自动解析参数
        self._parameters = self._parse_parameters()

    def _extract_description_from_docstring(self) -> str:
        """从 docstring 提取描述"""
        doc = inspect.getdoc(self.method)
        if not doc:
            return f"执行 {self.method.__name__}"

        # 提取第一行作为描述
        lines = doc.split('\n')
        for line in lines:
            line = line.strip()
            if line and not line.startswith('Args:') and not line.startswith('Returns:'):
                return line

        return f"执行 {self.method.__name__}"

    def _parse_parameters(self) -> List[ToolParameter]:
        """从方法签名和 docstring 自动提取参数"""
        sig = inspect.signature(self.method)
        type_hints = get_type_hints(self.method)
        docstring = inspect.getdoc(self.method) or ""

        # 从 docstring 解析参数描述
        param_descriptions = self._parse_param_descriptions(docstring)

        parameters = []
        for param_name, param in sig.parameters.items():
            if param_name == 'self':
                continue

            # 获取类型
            param_type_hint = type_hints.get(param_name, str)
            param_type = self._python_type_to_tool_type(param_type_hint)

            # 判断是否必需
            required = param.default == inspect.Parameter.empty
            default = None if required else param.default

            # 获取描述
            description = param_descriptions.get(param_name, f"参数 {param_name}")

            parameters.append(ToolParameter(
                name=param_name,
                type=param_type,
                description=description,
                required=required,
                default=default
            ))

        return parameters

    def _parse_param_descriptions(self, docstring: str) -> Dict[str, str]:
        """从 docstring 解析参数描述

        支持格式:
            Args:
                param_name: 参数描述
                another_param: 另一个参数描述
        """
        descriptions = {}

        # 查找 Args: 部分
        args_match = re.search(r'Args:\s*\n(.*?)(?:\n\s*\n|Returns:|$)', docstring, re.DOTALL)
        if not args_match:
            return descriptions

        args_section = args_match.group(1)

        # 解析每个参数
        # 匹配格式: param_name: 描述 或 param_name (type): 描述
        param_pattern = r'^\s*(\w+)(?:\s*\([^)]+\))?\s*:\s*(.+?)(?=^\s*\w+\s*(?:\([^)]+\))?\s*:|$)'
        matches = re.finditer(param_pattern, args_section, re.MULTILINE | re.DOTALL)

        for match in matches:
            param_name = match.group(1).strip()
            param_desc = match.group(2).strip()
            # 清理描述中的多余空白
            param_desc = re.sub(r'\s+', ' ', param_desc)
            descriptions[param_name] = param_desc

        return descriptions

    def _python_type_to_tool_type(self, py_type) -> str:
        """将 Python 类型转换为工具类型字符串"""
        # 处理泛型类型
        origin = getattr(py_type, '__origin__', None)
        if origin is not None:
            if origin is list:
                return "array"
            elif origin is dict:
                return "object"

        # 处理基本类型
        type_map = {
            str: "string",
            int: "integer",
            float: "number",
            bool: "boolean",
            list: "array",
            dict: "object",
        }

        return type_map.get(py_type, "string")

    def get_parameters(self) -> List[ToolParameter]:
        """获取参数列表"""
        return self._parameters

    def run(self, parameters: Dict[str, Any]) -> str:
        """执行方法"""
        return self.method(**parameters)
